package fr.bda.cloud.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Iterator;

import fr.bda.cloud.data.Bloc;

/**
 * This class parse a file an split it into N blocs and eval the parity bloc
 * 
 * @author BARET
 * @since 24/10/2012
 * 
 */
public class CloudFileParser implements Iterator<Bloc> {

	// #################### ERRORS ###################################

	/** Invalid file (Null or doesn't exist) */
	public static final String ERR_1 = "Invalid file (Null or doesn't exist)";

	/** Impossible to parse a directory */
	public static final String ERR_2 = "Impossible to parse a directory";

	/** Bloc size exceeded */
	public static final String ERR_3 = "Bloc size exceeded";

	/** Impossible to parse the file */
	public static final String ERR_4 = "Impossible to parse the file";

	/** The number of bloc must be greater than 0 */
	public static final String ERR_5 = "The number of bloc must be greater than 0";

	// ###############################################################

	/** The file to parse */
	File fileToParse;

	/** The bloc size */
	Long blocSize;

	/** The parity bloc to create */
	Bloc parityBloc;

	/** The number of bloc to get */
	int nbBlocs;

	/** The total number of bytes to read */
	Long totalToRead;

	/** Number of bytes read */
	Long totalRead;

	/** The iterator sync object */
	private Object iteratorSyncObject = new Object();

	/** The file to read */
	FileInputStream fileInputStream;

	/**
	 * 
	 * @param f
	 *            The file to parse
	 * @param nbBlocs
	 *            The number of blocs to create
	 * 
	 * @throws RuntimeException
	 *             <UL>
	 *             <LI><B>ERR_1</B> - If the file is null or doesn't exist</LI>
	 *             <LI><B>ERR_2</B> - If the file is a directory</LI>
	 *             <LI><B>ERR_3</B> - If the bloc size exceed Integer.MAX_VALUE.
	 *             If a big file is load and the number of bloc is not enough
	 *             important.</LI>
	 *             <LI><B>ERR_5</B> - If the number of bloc is not > 0</LI>
	 *             </UL>
	 * 
	 * @see CloudFileParser#ERR_1
	 * @see CloudFileParser#ERR_2
	 * @see CloudFileParser#ERR_3
	 * @see CloudFileParser#ERR_5
	 */
	public CloudFileParser(File f, int nbBlocs) {

		if (f == null || !f.exists()) {
			manageError(ERR_1);
		}
		if (f.isDirectory()) {
			manageError(ERR_2);
		}
		if (nbBlocs < 1) {
			manageError(ERR_5);
		}

		totalToRead = f.length();
		Long tmpBlocSize = totalToRead / nbBlocs;
		if (totalToRead % nbBlocs > 0) {
			tmpBlocSize++;
		}
		if (tmpBlocSize > Integer.MAX_VALUE) {
			manageError(ERR_3);
		}

		this.fileToParse = f;
		this.nbBlocs = nbBlocs;
		blocSize = tmpBlocSize;
	}

	public Bloc getParityBloc() {
		return parityBloc;
	}

	/**
	 * If no iterator in progress, this method return false
	 */
	@Override
	public boolean hasNext() {
		return fileInputStream != null;
	}

	/**
	 * Get an iterator to parse the file by bloc
	 * 
	 * @return The iterator to parse the file bloc by bloc
	 */
	public Iterator<Bloc> iterator() {

		synchronized (iteratorSyncObject) {

			if (fileInputStream != null) {
				return new CloudFileParser(fileToParse, nbBlocs).iterator();
			}

			try {
				fileInputStream = new FileInputStream(fileToParse);
				totalRead = 0L;
			} catch (FileNotFoundException e) {
				manageError(ERR_1);
			}

		}

		return this;
	}

	/**
	 * The next bloc or null if it's the end of file
	 * 
	 * @return The next bloc or null
	 */
	@Override
	public Bloc next() {
		if (fileInputStream != null) {

			synchronized (iteratorSyncObject) {

				Long byteTabSize = totalToRead - totalRead;

				if (byteTabSize > blocSize) {
					byteTabSize = blocSize;
				}
				Bloc b = new Bloc(blocSize.intValue());
				byte[] bytes = new byte[byteTabSize.intValue()];
				try {
					int nbRead = fileInputStream.read(bytes);
					totalRead += nbRead;
				} catch (IOException e) {
					manageError(ERR_4);
				}
				b.setData(bytes);
				if (parityBloc == null) {
					parityBloc = new Bloc(b.getData());
				} else {
					parityBloc = parityBloc.xor(b);
				}

				// Close the file if all is read
				if (totalRead >= totalToRead) {
					try {
						fileInputStream.close();
					} catch (IOException e) {
						manageError("Impossible to close the file [" + fileToParse.getAbsolutePath() + "]", e);
					}
					fileInputStream = null;
				}
				return b;

			}
		}
		return null;
	}

	@Override
	public void remove() {
		// No sense here
	}

	private void manageError(String errorMess, Throwable... parentException) {
		if (fileInputStream != null) {
			try {
				fileInputStream.close();
			} catch (IOException e) {

			}
			fileInputStream = null;
		}
		if (parentException != null && parentException.length == 1 && parentException[0] != null) {
			throw new RuntimeException(errorMess, parentException[0]);
		} else {
			throw new RuntimeException(errorMess);
		}
	}

	public static byte[] readFile(File f) {
		byte[] res = new byte[(int) f.length()];
		try (FileInputStream binFis = new FileInputStream(f);) {
			binFis.read(res);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return res;
	}
}
